#ifndef DENCLUE_FUNCTIONS_H
#define DENCLUE_FUNCTIONS_H

#include <iostream>
#include <cmath>
#include <assert.h>
#include "hyperspace.h"
#include "Clustering.h"
#include "DataImage.h"

using namespace std;

class HyperSpace;

class DenclueFunctions {
    public:
        static long double calculateInfluence( const DATA_ENTITY_CLASS_NAME&
            entity_one, const DATA_ENTITY_CLASS_NAME& entity_two , double sigma){
            long double distance = entity_one.euclidicDistance(entity_two);
            
            if( distance == 0 ){
                return 0;  
            }
            long double exponent = - powl( distance , 2) / (2.0 * powl(sigma,2) );
            long double influence = expl(exponent);
            return influence;
        }

        static long double calculateDensity( const DATA_ENTITY_CLASS_NAME& entity, HyperSpace::EntityIterator iter , double sigma){
            long double density = 0;
            while( !iter.end() ){

                density += DenclueFunctions::calculateInfluence( entity, *iter, sigma );
                iter++;
            }
            
            return density;
        }

        static vector<double> calculateGradient( const DATA_ENTITY_CLASS_NAME& entity, HyperSpace::EntityIterator iter, double sigma ){
            vector<double> gradient;
            for( unsigned i=0 ; i < entity.getNumOfDimensions(); i++){
                gradient.push_back(0);
            }

            for( ; !iter.end() ; iter++){
                const DATA_ENTITY_CLASS_NAME& other_entity = *iter;
                double curr_influence = DenclueFunctions::calculateInfluence(entity, other_entity, sigma);
                
                for(unsigned i=0 ; i < entity.getNumOfDimensions() ; i++){
                    double curr_difference = (iter->getComponentValue(i) - entity.getComponentValue(i));
                    gradient[i] += curr_difference * curr_influence;
                }
            }
            return gradient;
        }

        static const DATA_ENTITY_CLASS_NAME getDensityAttractor( const DATA_ENTITY_CLASS_NAME& entity, const HyperSpace& spatial_region, HyperSpace::EntityIterator iter, double sigma ){
            const double delta = 1;
            HyperSpace::EntityIterator initial_iter = iter;
            
            DATA_ENTITY_CLASS_NAME curr_attractor(entity);
            DATA_ENTITY_CLASS_NAME *found_attractor = NULL;
            
            unsigned MAX_ITERATIONS = 100;  //1000
            bool reachedTop = false;
            do{
                
                if( --MAX_ITERATIONS <= 0 )  break;
                
                DATA_ENTITY_CLASS_NAME last_attractor(curr_attractor);
                
                HyperSpace::EntityIterator gradient_iter = initial_iter;
                vector<double> curr_gradient =
                    DenclueFunctions::calculateGradient(last_attractor, initial_iter,
                            sigma);
                
                ostringstream grad_entity_str;
                for(unsigned i=0 ; i < curr_gradient.size() ; i++){
                    if( i != 0 ) grad_entity_str << ',';
                    grad_entity_str << curr_gradient[i];
                }
                grad_entity_str << '\n';
                DATA_ENTITY_CLASS_NAME grad_entity;
                grad_entity.entityFromString(grad_entity_str.str());
                
                long double grad_entity_norm = grad_entity.getEuclideanNorm();
                assert( grad_entity_norm > 0 );
                curr_attractor = last_attractor + ( grad_entity * ((long double)(delta/grad_entity_norm)) );
                
                HyperSpace::EntityIterator density_iter = initial_iter;
                density_iter.begin();
                double curr_density = calculateDensity( curr_attractor, density_iter, sigma );
                curr_attractor.setDensity(curr_density);
                
                reachedTop = ( curr_attractor.getDensity() < last_attractor.getDensity() );
                if( reachedTop ) found_attractor = new DATA_ENTITY_CLASS_NAME(last_attractor);
            }while( !reachedTop );
            if( MAX_ITERATIONS <= 0 )  found_attractor = new DATA_ENTITY_CLASS_NAME(curr_attractor);
            return *found_attractor;
        }

        static bool pathBetweenExists( const DATA_ENTITY_CLASS_NAME& attractor1,
        const DATA_ENTITY_CLASS_NAME& attractor2, HyperSpace& hs,  double xi, double
        sigma, map<string, bool>& usedEntities ){
            usedEntities[ attractor1.getStringRepresentation() ] = true;
            usedEntities[ attractor2.getStringRepresentation() ] = true;
            
            if( attractor1.euclidicDistance(&attractor2) <= sigma ){
                return true;
            }
            vector<HyperSpace::EntityIterator>  curr_path;  
            HyperSpace::EntityIterator iter(hs);
            iter.begin();
            while( usedEntities.size() <= hs.getNumEntities() ){
                
                if( iter.end() ){
                    if( curr_path.empty() )  return false;  
                    
                    if( curr_path.size() < (hs.getNumEntities() - 2) ){
                        
                        usedEntities[ curr_path.back()->getStringRepresentation() ] = false;
                        curr_path.pop_back();
                        
                        if( curr_path.empty() )  return false;
                        
                        bool isUsed = false;
                        bool reachedEnd = false;
                        do{
                            curr_path.back()++;  
                            reachedEnd = ( curr_path.back().end() );
                            if( reachedEnd ) break;  
                            isUsed = usedEntities[
                                curr_path.back()->getStringRepresentation() ];

                        }while( isUsed );
                        iter = curr_path.back();  
                        if( reachedEnd ) continue;
                        if( !curr_path.empty() ){
                            curr_path.pop_back();
                        }
                        else  return false;
                    }
                    else{ 
                        return false;
                    }
                }
                
                const DATA_ENTITY_CLASS_NAME& curr_entity = *iter;
                if( usedEntities.count(curr_entity.getStringRepresentation()) > 0 ){
                    if( usedEntities[curr_entity.getStringRepresentation()] ){
                        iter++;
                        continue;
                    }
                }
                
                const DATA_ENTITY_CLASS_NAME& curr_path_end = curr_path.empty() ? attractor1 : *curr_path.back() ;
                if( (curr_entity.getDensity() >= xi) && ( curr_path_end.euclidicDistance(&curr_entity ) < sigma ) ){
                    
                    curr_path.push_back( iter );
                    usedEntities[curr_entity.getStringRepresentation()] = true;
                    if( curr_entity.euclidicDistance(&attractor2) < sigma ){
                        return true;
                    }
                    else{  
                        iter.begin();
                        continue;
                    }
                }
                iter++;
            }

            return false;
        }

        static void AppendVector( vector<DATA_ENTITY_CLASS_NAME>& dest, const
                vector<DATA_ENTITY_CLASS_NAME>& src){
            for( unsigned i=0 ; i < src.size() ; i++){
                dest.push_back(src[i]);
            }
            return;
        }
};

#endif
